#pragma(once)

#include <iostream>
#include <queue>
#include <list>
#include <deque>
#include <memory>
#include <vector>
#include <cstdlib>
#include <string>
#include <stdexcept>
#include <algorithm>

//这里基址是假设的
#define GPIOA_BASE (0x3A4B0000U)
#define GPIOB_BASE (0x3A4B0000U)
#define GPIOC_BASE (0x3A4B0000U)

//假设这是GPIO的寄存器地址映射
struct GPIO_REG_T {
    uint32_t CRL;
    uint32_t CRH;
    uint32_t BSRR;
    uint32_t BRR;
    uint32_t IDR;
};

//指针类型转换
#define PORT(base)  ((volatile GPIO_REG_T *)(base))

//GPIO模式枚举
enum Mode
{
    PPOut = 0,   // 推挽输出
    ODOut = 1,   // 开漏输出
    AFPPOut = 2, // 复用推挽输出
    AFODOut = 3, //复用开漏输出
    FLOIn = 4,  //浮空输入
};

struct mode {
    template<Mode m> //值类型的模板形参
    static constexpr Mode value = m; //c++14 变量模板，实现编译期计算
};

template <uint32_t Base, uint8_t Pin> //值类型的模板参数
struct Gpio {

    template<Mode m> //值类型的模板参数
    static inline void set_mode() {
        std::cout << "trigger set_mode" << std::endl;
        static_assert(Pin < 16, "pin must < 16"); //编译期断言
        constexpr uint8_t val = mode::value<m>;  //c++14 变量模板

        if constexpr (Pin < 8) {  //对GPIO的低位和高位pin，做判断后，配置相应寄存器,c++14, 编译期if
            constexpr uint32_t offset = 4 * Pin;  //c++14, 编译期计算
            constexpr uint32_t mask = 0x0F << offset; 
        //    PORT(Base)->CRL = (PORT(Base)->CRL & (~mask)) | (val << offset); //在真实硬件上运行时打开这行
        } else {
            constexpr uint32_t offset = 4 * (Pin-8); 
            constexpr uint32_t mask = 0x0F << offset; 
        //    PORT(Base)->CRH = (PORT(Base)->CRH & (~mask)) | (val << offset);  //在真实硬件上运行时打开这行
        }
    }

    //下面的这些在编译时，需要模板推导出BASE和Pin的类型
    static inline void set() { 
        std::cout << "trigger set" << std::endl;
        //PORT(Base)->BSRR = 0x01 << Pin; 
    }

    static inline void clr() {
        std::cout << "trigger clr" << std::endl;
        //PORT(Base)->BRR = 0x01 << Pin; 
    }

    static inline bool read() { 
        return (PORT(Base)->IDR & (0x01 << Pin))!=0;
    }
};

template <Mode m, typename... Pins>
inline void config_pins() {
    //不加template会有歧义，参考链接
    //https://blog.csdn.net/weixin_42062018/article/details/120665454
    //https://stackoverflow.com/questions/15572415/expected-primary-expression-before-in-g-but-not-in-microsoft-compiler
    (Pins::template set_mode<m>(), ...); 
}

//可变参模板，Ts是可变参的第一个实参,模板会递归的调用自身
//由于 C++ 允许在类内定义类型别名，且其使用方法与通过类型名访问类成员的方法相同。
//故而，在类定义不可知的时候，编译器无法知晓类似 T::foo的写法具体指的是一个类型还是类内成员。
template <typename... Ts> // typename关键字指定模板参数是个类型，而不是类。在函数模板里用
inline void config_pp_out() {
    //在这个参数表里的gpio，都是PPOut模式，会递归调用config_pins函数，直到找到最终实现(即递归出口函数),
    //每次递归，都会展开Ts表里的一个项，直到Ts表里为空，就会调用config_pins(void)
    //可以看出，这里存在约束关系，即参数表里最后一个最先调用。
    config_pins<Mode::PPOut, Ts...>(); 
    (Ts::clr(), ...); //递归调用每个gpio的clr函数，这里Ts是Ts...的一个表项，即某个gpio，这个也是倒序的，因为递归入口的后面代码都是倒序的
}

template <typename... Ts>
inline void config_od_out() {
    config_pins<Mode::ODOut, Ts...>();
    (Ts::set(), ...);//折叠表达式，c++17
}

template <typename... Ts>
inline void config_af_pp_out() {
    config_pins<Mode::AFPPOut, Ts...>();
}

//实例化模板的GPIO组，传入GPIO组的基地址,定义GPIO组的类型别名
template <uint8_t Pin>
using PA = Gpio<GPIOA_BASE, Pin>;

template <uint8_t Pin>
using PB = Gpio<GPIOB_BASE, Pin>;

template <uint8_t Pin>
using PC = Gpio<GPIOC_BASE, Pin>;

//定义iic的2个引脚
using sda = PA<6>;
using scl = PC<3>;

//所有gpio初始化配置
class Pinctrl 
{
    public:
        Pinctrl() {
            //这里给出电源和时钟的配置
            //...
            //按要初始化的类型，分组做初始化
            config_pp_out<sda, scl>();
        };

    private:
};

void GpioTest(void);


//可变参模板的展开主要有3种方式
//参考链接https://blog.csdn.net/albertsh/article/details/123978539
// 递归展开  c++11
// 初始化列表展开(使用逗号表达式) c++11
// 折叠表达式展开 c++17

//下面给出一个利用递归可变参的例子
#if 0
class ArgBase
{
public:
    ArgBase() {}
    virtual ~ArgBase() {}
    virtual void PrintArgs() = 0;
};

template <typename T>
class Arg : public ArgBase
{
public:
    Arg(T arg) : m_Arg(arg) {}
    virtual ~Arg() {}
    virtual void PrintArgs()
    {
        std::cout << m_Arg << std::endl;
    }
public:
    T m_Arg;
};

class ArgArray : public std::vector<ArgBase*>
{
public:
    ArgArray() {}
    ~ArgArray()
    {
        std::for_each(begin(), end(), [](ArgBase* p) { delete p; });
    }
};


template <typename T>
static void ParseArgs(ArgArray& argArray, T t)
{
    argArray.push_back(new Arg<T>(t));
}

template<typename T, typename ...Args>
static void ParseArgs(ArgArray& argArray, T t, Args&& ...args)
{
    ParseArgs(argArray, t);
    ParseArgs(argArray, args...);
}


template<typename ...Args>
void ParseTest(Args&&... args)
{
    ArgArray argArray;
    ParseArgs(argArray, args...);

    // 打印可变参数列表中的参数
    for (int i = 0; i < argArray.size(); ++i)
    {
        argArray[i]->PrintArgs();
    }

}


int main()
{
    ParseTest(1, "hello", "world", 3.1415926);

    getchar();

    return 0;
}

#endif
